Applied Data Science Capstone - Week 3

Segmenting and Clustering Neighborhoods in Toronto

Contents

Scrape Wiki Page for Toronto's Postal Codes

Geocode

Additional Data for Toronto

Explore Neighborhoods Cluster Data Maps

In [ ]:
 
In [ ]:
####  Tip for links:    [Link to the destination](#the_destination)  followed by   <a id='K-Means_Clustering'></a>
In [ ]:
 
In [1]:
import requests
# http requests & response used with Foursquare API

import json # library to handle JSON files
import pandas as pd
from pandas.io.json import json_normalize # tranform JSON file into a pandas dataframe
# Foursquare results will be in JSON and then flattened and stored in a Pandas Dataframe
In [2]:
from bs4 import BeautifulSoup

# Beautiful Soup is a Python library for pulling data out of HTML and XML files.
In [3]:
import numpy as np # library to handle data in a vectorized manner

#!conda install -c conda-forge geopy --yes # uncomment this line if you haven't completed the Foursquare API lab
from geopy.geocoders import Nominatim # convert an address into latitude and longitude values

# Geopy can calculate geodesic distance between two points using the geodesic distance or the great-circle distance, 
# with a default of the geodesic distance available as the function geopy.distance.distance.
from geopy.distance import geodesic
from geopy.distance import great_circle

import scipy
from scipy.spatial import distance


# Matplotlib and associated plotting modules
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.colors as colors


# import k-means for clustering 
from sklearn.cluster import KMeans

#!conda install -c conda-forge folium=0.5.0 --yes # uncomment this line if you haven't completed the Foursquare API lab
import folium # map rendering library

print('Libraries imported.')
Libraries imported.
In [4]:
# folium should be version 0.11.0

print(folium.__version__)
0.11.0
In [5]:
import pgeocode

# pgeocode is a Python library for high performance off-line querying of GPS coordinates, region name and municipality
# name from postal codes. Distances between postal codes as well as general distance queries are also supported. 
# The used GeoNames database includes postal codes for 83 countries.
# Currently, only queries within the same country are supported.
# For additional documentation see pgeocode.readthedocs.io.
In [6]:
pgeocode.__version__
Out[6]:
'0.2.1'
In [7]:
import geopandas as gpd
import geojson
In [8]:
# Wide Display

from IPython.core.display import display, HTML
display(HTML("<style>.container { width:100% !important; }</style>"))
In [ ]:
 
In [ ]:
 
In [9]:
# Files saved: 
# 'North_York_Starbucks_20200616b.pkl'
# 'Toronto_Downtown_Starbucks_20200616b.pkl'
# 'Toronto_Scarborough_Starbucks_20200616b.pkl'
# 'Toronto_West_York_Starbucks_20200616b.pkl'

# df.to_pickle(file_name)  # where to save it, usually as a .pkl
# df = pd.read_pickle(file_name)
In [ ]:
 

Define Foursquare Credentials and Version

In [10]:
CLIENT_ID = '1O1KN544PAGAVX5SIEEO4SINNIYJTELH1YMHPSOPLESWN1VF' # your Foursquare ID
CLIENT_SECRET = '3VB4HIYI1IYA0SYA5L3TLAJM0P4KFGOZ45FRMYMSGXCR4FTV' # your Foursquare Secret
VERSION = '20200514' # Foursquare API version

print('Your credentails:')
print('CLIENT_ID: ' + CLIENT_ID)
print('CLIENT_SECRET:' + CLIENT_SECRET)
Your credentails:
CLIENT_ID: 1O1KN544PAGAVX5SIEEO4SINNIYJTELH1YMHPSOPLESWN1VF
CLIENT_SECRET:3VB4HIYI1IYA0SYA5L3TLAJM0P4KFGOZ45FRMYMSGXCR4FTV
In [11]:
# function that extracts the category of the venue from Foursquare results

def get_categories(row):
    try:
        categories_list = row['categories']
    except:
        categories_list = row['venue.categories']
        
    if len(categories_list) == 0:
        return None
    else:
        return categories_list[0]['name']
In [ ]:
 

Wikipedia page used to gather data that is in a table of postal codes and then transform the data into a pandas dataframe

In [13]:
# https://en.wikipedia.org/wiki/List_of_postal_codes_of_Canada:_M
# Metropolitan Toronto: M   102 Postal Codes for Toronto;  140 Neighbourhoods

url = 'http://en.wikipedia.org/wiki/List_of_postal_codes_of_Canada:_M'
In [14]:
response = requests.get(url)
In [15]:
# Beautiful Soup supports the HTML parser included in Python’s standard library, 
# but it also supports a number of third-party Python parsers. One is the lxml parser.

soup = BeautifulSoup(response.text, 'lxml')
In [16]:
# WORKS! For CSS Class wikitable sortable jquery
# If you want to search for tags that match two or more CSS classes, you should use a CSS selector:

# soup.select("table.wikitable.sortable")
In [17]:
postal_table = soup.select("table.wikitable.sortable")
In [18]:
# Use Pandas to read html table into a dataaframe.

postal_df = pd.read_html(str(postal_table))[0]
In [19]:
#PostalCode-Borough-Neighborhood-Dataframe

In [20]:
postal_df
Out[20]:
Postal Code Borough Neighborhood
0 M1A Not assigned Not assigned
1 M2A Not assigned Not assigned
2 M3A North York Parkwoods
3 M4A North York Victoria Village
4 M5A Downtown Toronto Regent Park, Harbourfront
... ... ... ...
175 M5Z Not assigned Not assigned
176 M6Z Not assigned Not assigned
177 M7Z Not assigned Not assigned
178 M8Z Etobicoke Mimico NW, The Queensway West, South of Bloor,...
179 M9Z Not assigned Not assigned

180 rows × 3 columns

In [21]:
postal_df.shape
Out[21]:
(180, 3)
In [22]:
# 77 rows have Borough values 'Not Assigned'
# 9 Boroughs within Toronto
# Mississauga is a city outside of Toronto but has one Toronto assigned postal code, M7R for its processing centre.

postal_df.Borough.value_counts()
Out[22]:
Not assigned        77
North York          24
Downtown Toronto    19
Scarborough         17
Etobicoke           12
Central Toronto      9
West Toronto         6
York                 5
East York            5
East Toronto         5
Mississauga          1
Name: Borough, dtype: int64
In [23]:
postal_df[postal_df['Borough'].str.contains('Not assigned')]
Out[23]:
Postal Code Borough Neighborhood
0 M1A Not assigned Not assigned
1 M2A Not assigned Not assigned
7 M8A Not assigned Not assigned
10 M2B Not assigned Not assigned
15 M7B Not assigned Not assigned
... ... ... ...
174 M4Z Not assigned Not assigned
175 M5Z Not assigned Not assigned
176 M6Z Not assigned Not assigned
177 M7Z Not assigned Not assigned
179 M9Z Not assigned Not assigned

77 rows × 3 columns

In [24]:
# Test if any Borough values do not match 'Not assigned', and there are not any.

postal_df[~postal_df['Borough'].str.contains('Not assigned')]
Out[24]:
Postal Code Borough Neighborhood
2 M3A North York Parkwoods
3 M4A North York Victoria Village
4 M5A Downtown Toronto Regent Park, Harbourfront
5 M6A North York Lawrence Manor, Lawrence Heights
6 M7A Downtown Toronto Queen's Park, Ontario Provincial Government
... ... ... ...
160 M8X Etobicoke The Kingsway, Montgomery Road, Old Mill North
165 M4Y Downtown Toronto Church and Wellesley
168 M7Y East Toronto Business reply mail Processing Centre, South C...
169 M8Y Etobicoke Old Mill South, King's Mill Park, Sunnylea, Hu...
178 M8Z Etobicoke Mimico NW, The Queensway West, South of Bloor,...

103 rows × 3 columns

Create new dataframe of rows where 'Borough' column is NOT string 'Not assigned'

In [25]:
# Create new dataframe of rows where 'Borough' column is NOT string 'Not assigned'

assigned_postal_df = postal_df[~postal_df['Borough'].str.contains('Not assigned')]
In [26]:
assigned_postal_df.shape
Out[26]:
(103, 3)
In [27]:
assigned_postal_df
Out[27]:
Postal Code Borough Neighborhood
2 M3A North York Parkwoods
3 M4A North York Victoria Village
4 M5A Downtown Toronto Regent Park, Harbourfront
5 M6A North York Lawrence Manor, Lawrence Heights
6 M7A Downtown Toronto Queen's Park, Ontario Provincial Government
... ... ... ...
160 M8X Etobicoke The Kingsway, Montgomery Road, Old Mill North
165 M4Y Downtown Toronto Church and Wellesley
168 M7Y East Toronto Business reply mail Processing Centre, South C...
169 M8Y Etobicoke Old Mill South, King's Mill Park, Sunnylea, Hu...
178 M8Z Etobicoke Mimico NW, The Queensway West, South of Bloor,...

103 rows × 3 columns

In [28]:
sorted_postal_df = assigned_postal_df.sort_values('Borough')
In [29]:
sorted_postal_df.head(50)
Out[29]:
Postal Code Borough Neighborhood
138 M4V Central Toronto Summerhill West, Rathnelly, South Hill, Forest...
120 M4S Central Toronto Davisville
129 M4T Central Toronto Moore Park, Summerhill East
103 M5P Central Toronto Forest Hill North & West, Forest Hill Road Park
112 M5R Central Toronto The Annex, North Midtown, Yorkville
102 M4P Central Toronto Davisville North
94 M5N Central Toronto Roselawn
111 M4R Central Toronto North Toronto West, Lawrence Park
93 M4N Central Toronto Lawrence Park
31 M5E Downtown Toronto Berczy Park
41 M6G Downtown Toronto Christie
40 M5G Downtown Toronto Central Bay Street
157 M5X Downtown Toronto First Canadian Place, Underground city
67 M5K Downtown Toronto Toronto Dominion Centre, Design Exchange
165 M4Y Downtown Toronto Church and Wellesley
121 M5S Downtown Toronto University of Toronto, Harbord
6 M7A Downtown Toronto Queen's Park, Ontario Provincial Government
147 M4W Downtown Toronto Rosedale
58 M5J Downtown Toronto Harbourfront East, Union Station, Toronto Islands
148 M5W Downtown Toronto Stn A PO Boxes
22 M5C Downtown Toronto St. James Town
49 M5H Downtown Toronto Richmond, Adelaide, King
4 M5A Downtown Toronto Regent Park, Harbourfront
76 M5L Downtown Toronto Commerce Court, Victoria Hotel
156 M4X Downtown Toronto St. James Town, Cabbagetown
13 M5B Downtown Toronto Garden District, Ryerson
139 M5V Downtown Toronto CN Tower, King and Spadina, Railway Lands, Har...
130 M5T Downtown Toronto Kensington Market, Chinatown, Grange Park
30 M4E East Toronto The Beaches
66 M4K East Toronto The Danforth West, Riverdale
75 M4L East Toronto India Bazaar, The Beaches West
84 M4M East Toronto Studio District
168 M7Y East Toronto Business reply mail Processing Centre, South C...
57 M4J East York East Toronto, Broadview North (Old East York)
12 M4B East York Parkview Hill, Woodbine Gardens
21 M4C East York Woodbine Heights
48 M4H East York Thorncliffe Park
39 M4G East York Leaside
107 M9P Etobicoke Westmount
169 M8Y Etobicoke Old Mill South, King's Mill Park, Sunnylea, Hu...
178 M8Z Etobicoke Mimico NW, The Queensway West, South of Bloor,...
116 M9R Etobicoke Kingsview Village, St. Phillips, Martin Grove ...
142 M8V Etobicoke New Toronto, Mimico South, Humber Bay Shores
143 M9V Etobicoke South Steeles, Silverstone, Humbergate, Jamest...
26 M9C Etobicoke Eringate, Bloordale Gardens, Old Burnhamthorpe...
151 M8W Etobicoke Alderwood, Long Branch
152 M9W Etobicoke Northwest, West Humber - Clairville
17 M9B Etobicoke West Deane Park, Princess Gardens, Martin Grov...
160 M8X Etobicoke The Kingsway, Montgomery Road, Old Mill North
8 M9A Etobicoke Islington Avenue, Humber Valley Village
In [30]:
sorted_postal_df.iloc[49:99]
Out[30]:
Postal Code Borough Neighborhood
8 M9A Etobicoke Islington Avenue, Humber Valley Village
114 M7R Mississauga Canada Post Gateway Processing Centre
100 M2P North York York Mills West
92 M3N North York Downsview
91 M2N North York Willowdale, Willowdale East
109 M2R North York Willowdale, Willowdale West
89 M9M North York Humberlea, Emery
2 M3A North York Parkwoods
85 M5M North York Bedford Park, Lawrence Manor East
3 M4A North York Victoria Village
5 M6A North York Lawrence Manor, Lawrence Heights
11 M3B North York Don Mills
14 M6B North York Glencairn
20 M3C North York Don Mills
46 M2H North York Hillcrest Village
47 M3H North York Bathurst Manor, Wilson Heights, Downsview North
56 M3J North York Northwood Park, York University
64 M2K North York Bayview Village
65 M3K North York Downsview
55 M2J North York Fairview, Henry Farm, Oriole
83 M3M North York Downsview
73 M2L North York York Mills, Silver Hills
74 M3L North York Downsview
82 M2M North York Willowdale, Newtonbrook
80 M9L North York Humber Summit
77 M6L North York North Park, Maple Leaf Park, Upwood Park
135 M1V Scarborough Milliken, Agincourt North, Steeles East, L'Amo...
90 M1N Scarborough Birch Cliff, Cliffside West
9 M1B Scarborough Malvern, Rouge
153 M1X Scarborough Upper Rouge
18 M1C Scarborough Rouge Hill, Port Union, Highland Creek
27 M1E Scarborough Guildwood, Morningside, West Hill
144 M1W Scarborough Steeles West, L'Amoreaux West
36 M1G Scarborough Woburn
45 M1H Scarborough Cedarbrae
72 M1L Scarborough Golden Mile, Clairlea, Oakridge
81 M1M Scarborough Cliffside, Cliffcrest, Scarborough Village West
117 M1S Scarborough Agincourt
126 M1T Scarborough Clarks Corners, Tam O'Shanter, Sullivan
108 M1R Scarborough Wexford, Maryvale
63 M1K Scarborough Kennedy Park, Ionview, East Birchmount Park
54 M1J Scarborough Scarborough Village
99 M1P Scarborough Dorset Park, Wexford Heights, Scarborough Town...
68 M6K West Toronto Brockton, Parkdale Village, Exhibition Place
113 M6R West Toronto Parkdale, Roncesvalles
59 M6J West Toronto Little Portugal, Trinity
104 M6P West Toronto High Park, The Junction South
122 M6S West Toronto Runnymede, Swansea
50 M6H West Toronto Dufferin, Dovercourt Village
86 M6M York Del Ray, Mount Dennis, Keelsdale and Silverthorn
In [31]:
sorted_postal_df.iloc[98:104]
Out[31]:
Postal Code Borough Neighborhood
86 M6M York Del Ray, Mount Dennis, Keelsdale and Silverthorn
23 M6C York Humewood-Cedarvale
32 M6E York Caledonia-Fairbanks
98 M9N York Weston
95 M6N York Runnymede, The Junction North
In [ ]:
 
In [32]:
# Cleaned-Sorted-PostalCode-Dataframe

In [33]:
sorted_postal_df.head(10)
Out[33]:
Postal Code Borough Neighborhood
138 M4V Central Toronto Summerhill West, Rathnelly, South Hill, Forest...
120 M4S Central Toronto Davisville
129 M4T Central Toronto Moore Park, Summerhill East
103 M5P Central Toronto Forest Hill North & West, Forest Hill Road Park
112 M5R Central Toronto The Annex, North Midtown, Yorkville
102 M4P Central Toronto Davisville North
94 M5N Central Toronto Roselawn
111 M4R Central Toronto North Toronto West, Lawrence Park
93 M4N Central Toronto Lawrence Park
31 M5E Downtown Toronto Berczy Park
In [34]:
sorted_postal_df.shape
Out[34]:
(103, 3)
In [35]:
# 9 Boroughs listed in table
In [36]:
# NOTE: Some Postal Codes have multiple Neighborhoods assigned.
In [37]:
# City of Toronto’s 140 neighbourhoods displayed by neighbourhood number
# https://www.toronto.ca/city-government/data-research-maps/neighbourhoods-communities/neighbourhood-profiles/
In [38]:
# A forward sortation area (FSA) is a geographical region in which all postal codes start with the same three characters.
# The first letter of an FSA code denotes a particular "postal district", "M" for Toronto.
# The digit identifies the FSA as urban or rural. A zero indicates a wide-area rural region; all other digits indicate urban areas. 
# Note: There are no rural FSAs in Toronto, hence no postal codes should start with M0.
# The second letter represents a specific rural region, an entire medium-sized city, or a section of a major metropolitan area.

# Special example is NORTH POLE  H0H 0H0

# Toronto has 180 Postal Codes reserved but 77 are Not Assigned, leaving 103 Assigned Postal Codes.
In [ ]:
 

Create a list of Postal Codes from the dataframe

In [39]:
PC_array = assigned_postal_df['Postal Code'].to_numpy()
In [40]:
PC_array
Out[40]:
array(['M3A', 'M4A', 'M5A', 'M6A', 'M7A', 'M9A', 'M1B', 'M3B', 'M4B',
       'M5B', 'M6B', 'M9B', 'M1C', 'M3C', 'M4C', 'M5C', 'M6C', 'M9C',
       'M1E', 'M4E', 'M5E', 'M6E', 'M1G', 'M4G', 'M5G', 'M6G', 'M1H',
       'M2H', 'M3H', 'M4H', 'M5H', 'M6H', 'M1J', 'M2J', 'M3J', 'M4J',
       'M5J', 'M6J', 'M1K', 'M2K', 'M3K', 'M4K', 'M5K', 'M6K', 'M1L',
       'M2L', 'M3L', 'M4L', 'M5L', 'M6L', 'M9L', 'M1M', 'M2M', 'M3M',
       'M4M', 'M5M', 'M6M', 'M9M', 'M1N', 'M2N', 'M3N', 'M4N', 'M5N',
       'M6N', 'M9N', 'M1P', 'M2P', 'M4P', 'M5P', 'M6P', 'M9P', 'M1R',
       'M2R', 'M4R', 'M5R', 'M6R', 'M7R', 'M9R', 'M1S', 'M4S', 'M5S',
       'M6S', 'M1T', 'M4T', 'M5T', 'M1V', 'M4V', 'M5V', 'M8V', 'M9V',
       'M1W', 'M4W', 'M5W', 'M8W', 'M9W', 'M1X', 'M4X', 'M5X', 'M8X',
       'M4Y', 'M7Y', 'M8Y', 'M8Z'], dtype=object)
In [ ]:
 
In [ ]:
 

Geocoding Postal Codes

In [41]:
nomi = pgeocode.Nominatim('CA')
nomi.query_postal_code("M1W")
Out[41]:
postal_code                                                M1W
country code                                                CA
place_name        Scarborough (Steeles West / L'Amoreaux West)
state_name                                             Ontario
state_code                                                  ON
county_name                                        Scarborough
county_code                                                NaN
community_name                                             NaN
community_code                                             NaN
latitude                                               43.8016
longitude                                             -79.3216
accuracy                                                     6
Name: 0, dtype: object
In [42]:
# Create a new dataframe from query of postal codes for lat & lng 

postal_ll_df = nomi.query_postal_code(PC_array)
postal_ll_df.dtypes
Out[42]:
postal_code        object
country code       object
place_name         object
state_name         object
state_code         object
county_name        object
county_code       float64
community_name     object
community_code    float64
latitude          float64
longitude         float64
accuracy          float64
dtype: object
In [43]:
type(postal_ll_df)
Out[43]:
pandas.core.frame.DataFrame
In [44]:
postal_ll_df
Out[44]:
postal_code country code place_name state_name state_code county_name county_code community_name community_code latitude longitude accuracy
0 M3A CA North York (York Heights / Victoria Village / ... Ontario ON North York NaN NaN NaN 43.7545 -79.3300 1.0
1 M4A CA North York (Sweeney Park / Wigmore Park) Ontario ON NaN NaN NaN NaN 43.7276 -79.3148 6.0
2 M5A CA Downtown Toronto (Regent Park / Port of Toronto) Ontario ON Toronto 8133394.0 NaN NaN 43.6555 -79.3626 6.0
3 M6A CA North York (Lawrence Manor / Lawrence Heights) Ontario ON North York NaN NaN NaN 43.7223 -79.4504 6.0
4 M7A CA Queen's Park Ontario Provincial Government Ontario ON NaN NaN NaN NaN 43.6641 -79.3889 NaN
... ... ... ... ... ... ... ... ... ... ... ... ...
98 M8X CA Etobicoke (The Kingsway / Montgomery Road / Ol... Ontario ON Etobicoke NaN NaN NaN 43.6518 -79.5076 6.0
99 M4Y CA Downtown Toronto (Church and Wellesley) Ontario ON Toronto 8133394.0 NaN NaN 43.6656 -79.3830 6.0
100 M7Y CA East Toronto Business Reply Mail Processing Ce... Ontario ON Toronto 8133394.0 NaN NaN 43.7804 -79.2505 NaN
101 M8Y CA Etobicoke (Old Mill South / King's Mill Park /... Ontario ON Etobicoke NaN NaN NaN 43.6325 -79.4939 6.0
102 M8Z CA Etobicoke (Mimico NW / The Queensway West / So... Ontario ON Etobicoke NaN NaN NaN 43.6256 -79.5231 6.0

103 rows × 12 columns

In [45]:
postal_ll_df.columns
Out[45]:
Index(['postal_code', 'country code', 'place_name', 'state_name', 'state_code',
       'county_name', 'county_code', 'community_name', 'community_code',
       'latitude', 'longitude', 'accuracy'],
      dtype='object')
In [46]:
# filter columns
filtered_columns = ['postal_code', 'latitude', 'longitude']
postal_ll_df = postal_ll_df.loc[:, filtered_columns]
In [47]:
postal_ll_df
Out[47]:
postal_code latitude longitude
0 M3A 43.7545 -79.3300
1 M4A 43.7276 -79.3148
2 M5A 43.6555 -79.3626
3 M6A 43.7223 -79.4504
4 M7A 43.6641 -79.3889
... ... ... ...
98 M8X 43.6518 -79.5076
99 M4Y 43.6656 -79.3830
100 M7Y 43.7804 -79.2505
101 M8Y 43.6325 -79.4939
102 M8Z 43.6256 -79.5231

103 rows × 3 columns

In [48]:
assigned_postal_df.columns
Out[48]:
Index(['Postal Code', 'Borough', 'Neighborhood'], dtype='object')
In [49]:
postal_ll_df.columns
Out[49]:
Index(['postal_code', 'latitude', 'longitude'], dtype='object')
In [50]:
# Edit column so key on column name matches for merge
In [51]:
edit_postal_df = assigned_postal_df.rename(columns={'Postal Code': 'postal_code'})
In [52]:
edit_postal_df.columns
Out[52]:
Index(['postal_code', 'Borough', 'Neighborhood'], dtype='object')

Merge the two dataframes with common postal code values to assign latitude & longitude to borough/ neighborhoods.

In [55]:
# result = pd.merge(left, right, on='key')
In [56]:
postal_neighbourhood_ll = pd.merge(edit_postal_df, postal_ll_df, on='postal_code')

In [57]:
postal_neighbourhood_ll
Out[57]:
postal_code Borough Neighborhood latitude longitude
0 M3A North York Parkwoods 43.7545 -79.3300
1 M4A North York Victoria Village 43.7276 -79.3148
2 M5A Downtown Toronto Regent Park, Harbourfront 43.6555 -79.3626
3 M6A North York Lawrence Manor, Lawrence Heights 43.7223 -79.4504
4 M7A Downtown Toronto Queen's Park, Ontario Provincial Government 43.6641 -79.3889
... ... ... ... ... ...
98 M8X Etobicoke The Kingsway, Montgomery Road, Old Mill North 43.6518 -79.5076
99 M4Y Downtown Toronto Church and Wellesley 43.6656 -79.3830
100 M7Y East Toronto Business reply mail Processing Centre, South C... 43.7804 -79.2505
101 M8Y Etobicoke Old Mill South, King's Mill Park, Sunnylea, Hu... 43.6325 -79.4939
102 M8Z Etobicoke Mimico NW, The Queensway West, South of Bloor,... 43.6256 -79.5231

103 rows × 5 columns

Resulting Table of Postal Codes, Neighborhoods, Latitude & Longitude

In [52]:
# df.to_pickle(file_name)  
#     where to save it, usually as a .pkl

# df = pd.read_pickle(file_name)

postal_neighbourhood_ll.to_pickle('postal_neighbourhood_lat_lng_20200629.pkl')
In [ ]:
 

Load a new dataframe from Open Toronto Neighbourhoods csv file

In [58]:
# Download https://open.toronto.ca/dataset/neighbourhoods/
# https://ckan0.cf.opendata.inter.prod-toronto.ca/download_resource/a083c865-6d60-4d1d-b6c6-b0c8a85f9c15?format=geojson&projection=4326
# neighbourhood-profiles-2016-csv     https://ckan0.cf.opendata.inter.prod-toronto.ca/download_resource/ef0239b1-832b-4d0b-a1f3-4153e53b189e?format=csv
# Transpose Rows / Columns  .T

# toronto_profiles = pd.read_csv('neighbourhood-profiles-2016-csv.csv', index_col=0, header=None).T

# toronto_profiles = pd.read_csv('neighbourhood-profiles-2016-csv.csv', index_col=0, header=None)
# toronto_profiles = pd.read_csv('cleaned_neighbourhood-profiles-2016-csv_UTF8.csv', index_col=0).T
# toronto_profiles = pd.read_csv('Neighbourhoods_transpose_b_UTF8.csv', index_col=1)
toronto_profiles = pd.read_csv('Neighbourhoods_transpose_UTF8.csv', index_col=0)
In [59]:
toronto_profiles.columns
Out[59]:
Index(['NeighbourhoodNumber', 'Population2016', 'Population2011',
       'Population Change 2011-2016', 'Total private dwellings',
       'PopulationDensity_per_square_km', 'Land_area_in_square_km',
       'Children (0-14 years)', 'Youth (15-24 years)',
       'Working Age (25-54 years)', 'Pre-retirement (55-64 years)',
       'Seniors (65+ years)', 'Older Seniors (85+ years)',
       '  $100,000 and over', '    $200,000 and over'],
      dtype='object')
In [60]:
toronto_profiles.shape
Out[60]:
(140, 15)
In [61]:
toronto_profiles.head(10)
Out[61]:
NeighbourhoodNumber Population2016 Population2011 Population Change 2011-2016 Total private dwellings PopulationDensity_per_square_km Land_area_in_square_km Children (0-14 years) Youth (15-24 years) Working Age (25-54 years) Pre-retirement (55-64 years) Seniors (65+ years) Older Seniors (85+ years) $100,000 and over $200,000 and over
Neighbourhood
Agincourt North 129 29,113 30,279 -3.90% 9,371 3,929 7.41 3,840 3,705 11,305 4,230 6,045 925 2,505 325
Agincourt South-Malvern West 128 23,757 21,988 8.00% 8,535 3,034 7.83 3,075 3,360 9,965 3,265 4,105 555 2,030 285
Alderwood 20 12,054 11,904 1.30% 4,732 2,435 4.95 1,760 1,235 5,220 1,825 2,015 320 1,915 360
Annex 95 30,526 29,177 4.60% 18,109 10,863 2.81 2,360 3,750 15,040 3,480 5,910 1,040 5,895 2,670
Banbury-Don Mills 42 27,695 26,918 2.90% 12,473 2,775 9.98 3,605 2,730 10,810 3,555 6,975 1,640 4,615 1,750
Bathurst Manor 34 15,873 15,434 2.80% 6,418 3,377 4.70 2,325 1,940 6,655 2,030 2,940 710 2,025 515
Bay Street Corridor 76 25,797 19,348 33.30% 18,436 14,097 1.83 1,695 6,860 13,065 1,760 2,420 330 3,600 1,105
Bayview Village 52 21,396 17,671 21.10% 10,111 4,195 5.10 2,415 2,505 10,310 2,540 3,615 610 3,010 855
Bayview Woods-Steeles 49 13,154 13,530 -2.80% 4,895 3,240 4.06 1,515 1,635 4,490 1,825 3,685 740 1,830 580
Bedford Park-Nortown 39 23,236 23,185 0.20% 9,052 4,209 5.52 4,555 3,210 8,410 3,075 3,980 660 4,745 2,915
In [62]:
# df.sort_values(by=['col1'])

toronto_profiles_nn_sort = toronto_profiles.sort_values(by=['NeighbourhoodNumber'])
toronto_profiles_nn_sort.head(10)
Out[62]:
NeighbourhoodNumber Population2016 Population2011 Population Change 2011-2016 Total private dwellings PopulationDensity_per_square_km Land_area_in_square_km Children (0-14 years) Youth (15-24 years) Working Age (25-54 years) Pre-retirement (55-64 years) Seniors (65+ years) Older Seniors (85+ years) $100,000 and over $200,000 and over
Neighbourhood
West Humber-Clairville 1 33,312 34,100 -2.30% 11,045 1,117 29.81 5,060 5,445 13,845 3,990 4,980 615 3,085 310
Mount Olive-Silverstone-Jamestown 2 32,954 32,788 0.50% 10,220 7,291 4.52 7,090 5,240 13,615 3,475 3,560 300 1,705 110
Thistletown-Beaumond Heights 3 10,360 10,138 2.20% 3,472 3,130 3.31 1,730 1,410 4,160 1,195 1,880 350 940 120
Rexdale-Kipling 4 10,529 10,488 0.40% 3,989 4,229 2.49 1,640 1,355 4,300 1,520 1,730 300 1,040 95
Elms-Old Rexdale 5 9,456 9,550 -1.00% 3,344 3,306 2.86 1,805 1,440 3,700 1,255 1,275 145 730 60
Kingsview Village-The Westway 6 22,000 21,723 1.30% 8,159 4,356 5.05 4,240 3,020 8,635 2,550 3,585 575 1,875 330
Willowridge-Martingrove-Richview 7 22,156 21,343 3.80% 8,721 4,007 5.53 3,555 2,625 8,140 2,905 4,905 885 2,895 655
Humber Heights-Westmount 8 10,948 10,583 3.40% 4,261 3,981 2.75 1,450 1,140 3,790 1,510 3,045 950 1,340 320
Edenbridge-Humber Valley 9 15,535 14,943 4.00% 6,606 2,840 5.47 2,120 1,805 5,940 2,385 3,290 665 2,575 1,290
Princess-Rosethorn 10 11,051 11,197 -1.30% 3,958 2,138 5.17 1,770 1,580 3,825 1,855 2,025 325 2,450 1,270

Change Population Density string data with ',' to type integer

In [63]:
# PopulationDensity_per_square_km is a String with a , to represent numbers over 999.
# Change to int

toronto_profiles_nn_sort.dtypes
Out[63]:
NeighbourhoodNumber                  int64
Population2016                      object
Population2011                      object
Population Change 2011-2016         object
Total private dwellings             object
PopulationDensity_per_square_km     object
Land_area_in_square_km             float64
Children (0-14 years)               object
Youth (15-24 years)                 object
Working Age (25-54 years)           object
Pre-retirement (55-64 years)        object
Seniors (65+ years)                 object
Older Seniors (85+ years)           object
  $100,000 and over                 object
    $200,000 and over               object
dtype: object
In [64]:
df = toronto_profiles_nn_sort.copy()
In [65]:
df['PopulationDensity_per_square_km'] = df['PopulationDensity_per_square_km'].replace({',': ''}, regex=True).astype(int)
df.dtypes
Out[65]:
NeighbourhoodNumber                  int64
Population2016                      object
Population2011                      object
Population Change 2011-2016         object
Total private dwellings             object
PopulationDensity_per_square_km      int64
Land_area_in_square_km             float64
Children (0-14 years)               object
Youth (15-24 years)                 object
Working Age (25-54 years)           object
Pre-retirement (55-64 years)        object
Seniors (65+ years)                 object
Older Seniors (85+ years)           object
  $100,000 and over                 object
    $200,000 and over               object
dtype: object

In [66]:
df.head(7)
Out[66]:
NeighbourhoodNumber Population2016 Population2011 Population Change 2011-2016 Total private dwellings PopulationDensity_per_square_km Land_area_in_square_km Children (0-14 years) Youth (15-24 years) Working Age (25-54 years) Pre-retirement (55-64 years) Seniors (65+ years) Older Seniors (85+ years) $100,000 and over $200,000 and over
Neighbourhood
West Humber-Clairville 1 33,312 34,100 -2.30% 11,045 1117 29.81 5,060 5,445 13,845 3,990 4,980 615 3,085 310
Mount Olive-Silverstone-Jamestown 2 32,954 32,788 0.50% 10,220 7291 4.52 7,090 5,240 13,615 3,475 3,560 300 1,705 110
Thistletown-Beaumond Heights 3 10,360 10,138 2.20% 3,472 3130 3.31 1,730 1,410 4,160 1,195 1,880 350 940 120
Rexdale-Kipling 4 10,529 10,488 0.40% 3,989 4229 2.49 1,640 1,355 4,300 1,520 1,730 300 1,040 95
Elms-Old Rexdale 5 9,456 9,550 -1.00% 3,344 3306 2.86 1,805 1,440 3,700 1,255 1,275 145 730 60
Kingsview Village-The Westway 6 22,000 21,723 1.30% 8,159 4356 5.05 4,240 3,020 8,635 2,550 3,585 575 1,875 330
Willowridge-Martingrove-Richview 7 22,156 21,343 3.80% 8,721 4007 5.53 3,555 2,625 8,140 2,905 4,905 885 2,895 655
In [62]:
# df.to_pickle(file_name)  
#     where to save it, usually as a .pkl

# df = pd.read_pickle(file_name)

df.to_pickle('neighbourhood_pop_density_20200629.pkl')
In [ ]:
 

In [67]:
# neighborhoods_geojson = Neighbourhoods.geojson
# https://open.toronto.ca/dataset/neighbourhoods/
# https://github.com/jasonicarter/toronto-geojson/blob/master/toronto_crs84.geojson

# Boundaries of City of Toronto Neighbourhoods.
# https://ckan0.cf.opendata.inter.prod-toronto.ca/dataset/neighbourhoods/resource/a083c865-6d60-4d1d-b6c6-b0c8a85f9c15?view_id=204f29d7-2984-4e81-baed-3f5ed0582b8d

# http://www.arcgis.com/home/webmap/viewer.html?url=https://services.arcgis.com/As5CFN3ThbQpy8Ph/ArcGIS/rest/services/Toronto_Neighbourhoods/FeatureServer/0&source=sd

In [68]:
# Toronto City Hall   43.653908, -79.384293
# Both GeoJSON and TopoJSON layers can be passed to the map as an overlay, and multiple layers can be visualized on the same map
# 'r'	Open for reading (default)

neighborhood_boundaries = r'Neighbourhoods.geojson'

map_toronto_neigh = folium.Map(location=[43.653908, -79.384293], zoom_start=11)

folium.GeoJson(
    neighborhood_boundaries,
    name='geojson'
).add_to(map_toronto_neigh)

folium.LayerControl().add_to(map_toronto_neigh)

map_toronto_neigh
Out[68]:

Map of Toronto with geojson overlay showing all neighbourhood boundaries.

In [ ]:
 
In [69]:
neighborhood_boundaries
Out[69]:
'Neighbourhoods.geojson'
In [71]:
with open('Neighbourhoods.geojson') as f:
    boundaries = geojson.load(f)
features = boundaries['features'][0]
In [72]:
neigh_code = boundaries['features'][0]['properties']
type(neigh_code)
Out[72]:
dict
In [73]:
neigh_code.keys()
Out[73]:
dict_keys(['_id', 'AREA_ID', 'AREA_ATTR_ID', 'PARENT_AREA_ID', 'AREA_SHORT_CODE', 'AREA_LONG_CODE', 'AREA_NAME', 'AREA_DESC', 'X', 'Y', 'LONGITUDE', 'LATITUDE', 'OBJECTID', 'Shape__Area', 'Shape__Length'])
In [74]:
# print(Dict[1]) 

neigh_code['AREA_SHORT_CODE']
Out[74]:
94
In [75]:
boundaries['features'][0]['properties']
Out[75]:
{'_id': 5321,
 'AREA_ID': 25886861,
 'AREA_ATTR_ID': 25926662,
 'PARENT_AREA_ID': 49885,
 'AREA_SHORT_CODE': 94,
 'AREA_LONG_CODE': 94,
 'AREA_NAME': 'Wychwood (94)',
 'AREA_DESC': 'Wychwood (94)',
 'X': None,
 'Y': None,
 'LONGITUDE': -79.425514947,
 'LATITUDE': 43.6769192679,
 'OBJECTID': 16491505,
 'Shape__Area': 3217959.609375,
 'Shape__Length': 7515.779658331329}
In [76]:
# Access single neighbourhood code for first entry

boundaries['features'][0]['properties']['AREA_SHORT_CODE']
Out[76]:
94
In [ ]:
 

Use 'df' dataframe, (aka 'neighbourhood_pop_density_20200629.pkl') column -

PopulationDensity_per_square_km to calculate quantiles/ bins for folium.Choropleth

In [69]:
# Find quantiles for color scale.

print('max: ', df.PopulationDensity_per_square_km.max())
print('75%: ', df.PopulationDensity_per_square_km.quantile(.75))
print('60%: ', df.PopulationDensity_per_square_km.quantile(.60))
print('45%: ', df.PopulationDensity_per_square_km.quantile(.45))
print('30%: ', df.PopulationDensity_per_square_km.quantile(.30))
print('15%: ', df.PopulationDensity_per_square_km.quantile(.15))
print('min: ', df.PopulationDensity_per_square_km.min())
max:  44321
75%:  7621.25
60%:  6302.4
45%:  4717.35
30%:  3882.2
15%:  2853.6
min:  1040
In [ ]:
# population density choropleth map
# Population Density from 'df' dataframe. This is 'data' in folium.Choropleth
# Boundaries for neighbourhoods from file 'Neighbourhoods.geojson'

In [77]:
import json

# load geo_json
# shapefiles can be converted to geojson with QGIS
with open('Neighbourhoods.geojson') as f:
    boundaries_geo = json.load(f)
    
# add feature 'id' county name to geojson
# access features
for i in boundaries_geo['features']:
    i['id'] = i['properties']['AREA_SHORT_CODE']
    
new_boundaries_geo = boundaries_geo    
    
# load data associated with geo_json

    
# map    
map_choropleth = folium.Map(location=[43.653908, -79.384293], zoom_start=11)

# choropleth
folium.Choropleth(
    geo_data=new_boundaries_geo,
    name='choropleth',
    data=df,
    columns=['NeighbourhoodNumber','PopulationDensity_per_square_km'],
    # see folium.Choropleth? for details on key_on
    # key_on='feature.id',
    key_on='feature.properties.AREA_SHORT_CODE',
    fill_color='BuPu',
    #threshold_scale=[0, 2, 4, 8, 16, 32],
    #bins=9,
    #bins=[1000, 2000, 3000, 4000, 5000, 7500, 10000, 15000, 45000],
    bins=[1040, 2853, 3882, 4717, 6300, 7600, 45000],
    fill_opacity=0.5,
    line_opacity=0.5,
    legend_name='Population',
    highlight=True
).add_to(map_choropleth)

# layer control to turn choropleth on or off
folium.LayerControl().add_to(map_choropleth)

# display map
map_choropleth
Out[77]:

Choropleth map of neighborhoods color coded by population density

In [ ]:
 
In [ ]:
 

Foursquare Venue Data

The Places API offers real-time access to Foursquare’s global database of rich venue data and user content

to power your location-based experiences .....

In [ ]:
 

The TopPicks section of /venues/explore will produce results that vary by time of day.

Define function getVenuesTopPicks

In [12]:
def getVenuesTopPicks(near, categories):
    
    topPicks_all = pd.DataFrame(columns=['name', 'categories', 'address', 'lat', 'lng'])
    i = 0
    
    radius =20000
    section = 'topPicks'
    LIMIT = 50
    openNow = 1

    for categoryId in categories:
        url = 'https://api.foursquare.com/v2/venues/explore?&client_id={}&client_secret={}&v={}&near={}&radius={}&section={}&categoryId={}&limit={}&openNow={}'.format(
            CLIENT_ID, 
            CLIENT_SECRET, 
            VERSION, 
            near, 
            radius,
            section,
            categoryId,
            LIMIT,
            openNow)
        print(url) # display URL
    
                            
        # make the GET request
        results = requests.get(url).json()
        top_venues = results['response']['groups'][0]['items']
        top_venues_df = pd.json_normalize(top_venues) # flatten JSON

        # filter columns
        filtered_columns = ['venue.name', 'venue.categories', 'venue.location.address', 'venue.location.lat', 'venue.location.lng']
        nearby_top_venues = top_venues_df.loc[:, filtered_columns]
        
        # filter the category for each row
        nearby_top_venues['venue.categories'] = nearby_top_venues.apply(get_categories, axis=1)

        # clean columns
        nearby_top_venues.columns = [col.split(".")[-1] for col in nearby_top_venues.columns]
        
        topPicks_all = pd.concat([topPicks_all, nearby_top_venues])
        print('topPicks_all size: ', topPicks_all.shape)
        i = i +1

    topPicks_all = topPicks_all.drop_duplicates(subset='name', ignore_index=True)
    print('topPicks_all size: ', topPicks_all.shape)
          
    return(topPicks_all)

Run /venues/explore topPicks & pickle results dataframe

In [13]:
# getVenuesTopPicks function

# Run the above function
# Supply near as City, State, Country
# A string naming a place in the world. If the near string is not geocodable, returns a failed_geocode error. 
# Otherwise, searches within the bounds of the geocode and adds a geocode object to the response.

near = 'North York, ON, Canada'

# supply a list of Categories to explore top picks for.

# Categories:
# College & U. 4d4b7105d754a06372d81259;   Coffee Shop  4bf58dd8d48988d1e0931735;   Dessert Shop 4bf58dd8d48988d1d0941735;  
# Park 4bf58dd8d48988d163941735;   Botanical Garden 52e81612bcbc57f1066b7a22'
# Cafe'  4bf58dd8d48988d16d941735
# categoryId = '4bf58dd8d48988d16d941735'  

categories = ['4bf58dd8d48988d16d941735', '4bf58dd8d48988d1e0931735', '4bf58dd8d48988d1d0941735']

topPicks = getVenuesTopPicks(near, categories)
https://api.foursquare.com/v2/venues/explore?&client_id=1O1KN544PAGAVX5SIEEO4SINNIYJTELH1YMHPSOPLESWN1VF&client_secret=3VB4HIYI1IYA0SYA5L3TLAJM0P4KFGOZ45FRMYMSGXCR4FTV&v=20200514&near=North York, ON, Canada&radius=20000&section=topPicks&categoryId=4bf58dd8d48988d16d941735&limit=50&openNow=1
topPicks_all size:  (6, 5)
https://api.foursquare.com/v2/venues/explore?&client_id=1O1KN544PAGAVX5SIEEO4SINNIYJTELH1YMHPSOPLESWN1VF&client_secret=3VB4HIYI1IYA0SYA5L3TLAJM0P4KFGOZ45FRMYMSGXCR4FTV&v=20200514&near=North York, ON, Canada&radius=20000&section=topPicks&categoryId=4bf58dd8d48988d1e0931735&limit=50&openNow=1
topPicks_all size:  (15, 5)
https://api.foursquare.com/v2/venues/explore?&client_id=1O1KN544PAGAVX5SIEEO4SINNIYJTELH1YMHPSOPLESWN1VF&client_secret=3VB4HIYI1IYA0SYA5L3TLAJM0P4KFGOZ45FRMYMSGXCR4FTV&v=20200514&near=North York, ON, Canada&radius=20000&section=topPicks&categoryId=4bf58dd8d48988d1d0941735&limit=50&openNow=1
topPicks_all size:  (19, 5)
topPicks_all size:  (16, 5)
In [14]:
topPicks
Out[14]:
name categories address lat lng
0 The Only Cafe Café 972 Danforth Ave 43.680409 -79.337898
1 Library Bar Café 100 Front St. W 43.645500 -79.381602
2 Snakes & Lattes Café 600 Bloor St. W 43.664789 -79.413150
3 Sicilian Sidewalk Cafe Café 712 College St 43.655210 -79.418579
4 7 West Cafe Café 7 Charles St. W 43.668665 -79.386830
5 Cafe Palma Café North York city centre 43.765995 -79.410807
6 Second Cup Café 30 Bond Street 43.652981 -79.377408
7 Café Pamenar Café 307 Augusta Ave. 43.656678 -79.402822
8 The Walton Café 607 College St 43.655045 -79.414363
9 The Shmooz Café 590 Pape Ave 43.675171 -79.343395
10 Sarah's Cafe and Bar Café 1426 Danforth Avenue 43.682752 -79.327208
11 Tim Hortons Café 2606 Rutherford Road 43.834184 -79.520454
12 Dough Bakeshop Bakery 173 Danforth Ave. 43.676643 -79.356846
13 Absolute Bakery & Café Bakery 589 Parliament St 43.667469 -79.369277
14 Dessert Kitchen ç³–æ½® Dessert Shop 3623 Hwy 7. E 43.853865 -79.337135
15 Delicia Bakery & Pastry Bakery NaN 43.601403 -79.503012
In [ ]:
 

Pickle time sensitive results data so that it can stored offline and later retrieved for comparison.

The pickle module implements binary protocols for serializing and de-serializing a Python object structure.

“Pickling” is the process whereby a Python object hierarchy is converted into a byte stream, and “unpickling” is the inverse operation,

whereby a byte stream (from a binary file or bytes-like object) is converted back into an object hierarchy.

Pickling (and unpickling) is alternatively known as “serialization”

In [15]:
# df.to_pickle(file_name)  # where to save it, usually as a .pkl
# df = pd.read_pickle(file_name)

topPicks.to_pickle('north_york_coffee_cafe_dessert_topPicks_1210am_0702.pkl')
In [18]:
topPicks_7pm = pd.read_pickle('north_york_coffee_cafe_dessert_topPicks_720pm_0701.pkl')
In [19]:
topPicks_12am = topPicks
In [ ]:
 

Create a map to show TopPicks venues at 7pm compared to 12am to show to demonstate the "openNow" parameter.

In [20]:
# Midtown
# Toronto, ON, Canada

latitude = 43.698484 
longitude = -79.397192
In [21]:
#  parse_html=True

map_topPicks_openNow = folium.Map(location=[latitude, longitude], zoom_start=12, control_scale=True)


# I can add markers on the map with popup for Postal Code but borough name will not work reliably with variety of 
# characters, such as / . ''. Use folium.Popup with parse_html


        
# Coffee Cafe Desserts OpenNow 
for i in range(0,len(topPicks_7pm)):
    folium.Marker([topPicks_7pm.iloc[i]['lat'], topPicks_7pm.iloc[i]['lng']],
                  popup=folium.Popup(topPicks_7pm.iloc[i]['name'], parse_html=True),
                  icon=folium.Icon(color='blue', icon='info-sign')
                  ).add_to(map_topPicks_openNow) 
    
# Coffee Cafe Desserts OpenNow 
for i in range(0,len(topPicks)):
    folium.Marker([topPicks.iloc[i]['lat'], topPicks.iloc[i]['lng']],
                  popup=folium.Popup(topPicks.iloc[i]['name'], parse_html=True),
                  icon=folium.Icon(color='darkpurple', icon='info-sign')
                  ).add_to(map_topPicks_openNow) 
    

# m.save('index.html')
map_topPicks_openNow.save('map_TopPicks_openNow_12am_20200702.html')

map_topPicks_openNow
Out[21]:

Top Picks for Cafe', Dessert & Coffee cluster around downtown Toronto

In [ ]:
 
In [ ]:
 
In [26]:
# df = pd.read_pickle(file_name)

top_Starbucks_11am = pd.read_pickle('north_york_top_Starbucks_11am_0701.pkl')
top_Starbucks_11am.head(10)
Out[26]:
name categories address lat lng
0 Starbucks Coffee Shop 170 Rimrock Road, Unit 3 43.758597 -79.466252
1 Starbucks Coffee Shop 5140 Yonge St 43.768353 -79.413046
2 Starbucks Coffee Shop 2451 Yonge St N 43.711039 -79.398924
3 Starbucks Coffee Shop 4025 Yonge St 43.744871 -79.406289
4 Starbucks Coffee Shop 8010 Bathurst St, Building C, Unit 6 43.816777 -79.452391
5 Starbucks Coffee Shop 39 Eglinton Ave E 43.706859 -79.396950
6 Starbucks Coffee Shop 3050 Yonge St 43.724878 -79.402490
7 Starbucks Coffee Shop 626 Sheppard Avenue West 43.755797 -79.440471
8 Starbucks Coffee Shop 446 Spadina Rd 43.688970 -79.413097
9 Starbucks Coffee Shop 878 Eglinton Ave E 43.713756 -79.365021
In [ ]:
# map_TopPicks_day_latenight

In [30]:
# map_TopPicks_day_latenight

#  parse_html=True

# Midtown
# Toronto, ON, Canada

latitude = 43.698484 
longitude = -79.397192

map_topPicks = folium.Map(location=[latitude, longitude], zoom_start=12, control_scale=True)


# I can add markers on the map with popup for Postal Code but borough name will not work reliably with variety of 
# characters, such as / . ''. Use folium.Popup with parse_html
        
# Coffee OpenNow 
for i in range(0,len(top_Starbucks_11am)):
    folium.Marker([top_Starbucks_11am.iloc[i]['lat'], top_Starbucks_11am.iloc[i]['lng']],
                  popup=folium.Popup(top_Starbucks_11am.iloc[i]['name'], parse_html=True),
                  icon=folium.Icon(color='orange', icon='info-sign')
                  ).add_to(map_topPicks) 
    
# Coffee Cafe Desserts OpenNow 
for i in range(0,len(topPicks_7pm)):
    folium.Marker([topPicks_7pm.iloc[i]['lat'], topPicks_7pm.iloc[i]['lng']],
                  popup=folium.Popup(topPicks_7pm.iloc[i]['name'], parse_html=True),
                  icon=folium.Icon(color='blue', icon='info-sign')
                  ).add_to(map_topPicks) 
    
# Coffee Cafe Desserts OpenNow 
for i in range(0,len(topPicks)):
    folium.Marker([topPicks.iloc[i]['lat'], topPicks.iloc[i]['lng']],
                  popup=folium.Popup(topPicks.iloc[i]['name'], parse_html=True),
                  icon=folium.Icon(color='darkpurple', icon='info-sign')
                  ).add_to(map_topPicks) 
    

# m.save('index.html')
# map_topPicks_openNow.save('map_TopPicks_openNow_730pm_20200701.html')

map_topPicks
Out[30]:

North York coffee, cafe, dessert, topPicks.

Morning Starbucks Top Picks in Yellow; show customers satisfied with North York choices.

7pm Top Picks for coffee, cafe, desserts shown in blue.

12am Top Picks in dark purple. Both show strong preference for downtown Toronto.

In [ ]:
 

Foursquare /venues/search will produce all results that match query term and location.

Find All Starbucks in Toronto Metro area by searching in 5 areas, downtown, North, East & West of downtown.

Venues Searches are limited to 50 results & there are over 100 Starbucks locations.

Venues Search will not be affected by time of day and show all matching results.

In [ ]:
 
In [25]:
# Run a Venue Search with Query for Starbucks

# Toronto, ON, Canada- 

#1 Blue Jays Way
# Toronto, ON M5V 1J3, Canada
# 43.641853, -79.389716
# City Hall
# 43.653908, -79.384293

# Scarborough
# 43.777008, -79.224105


# 100 Scarlett Rd
# York, ON M6N 4K2, Canada
# 43.670158, -79.507366

# North York Centre
# Starbucks 5140 Yonge St, North York, ON M2N 6L7, Canada
# 43.768353, -79.413038

# Add East York
# Danforth Village
# Toronto, ON, Canada
# 43.687972, -79.301815
In [ ]:
 
In [26]:
# Coordinates to use for 4 Regions around Toronto with 15 km search radius each,

# [[ 43.777008, -79.224105]  East, Scarborough
# [ 43.768633, -79.413312 ]   North York
# [ 43.653908, -79.384293]   Downtown Toronto
# 43.670158, -79.507366  West, York, Etobicoke, ON
# 43.687972, -79.301815  East York

coordinates = np.array([43.777008, -79.224105, 43.768633, -79.413312, 43.653908, -79.384293, 43.670158, -79.507366, 43.687972, -79.301815 ]).reshape(5, 2)
coordinates
Out[26]:
array([[ 43.777008, -79.224105],
       [ 43.768633, -79.413312],
       [ 43.653908, -79.384293],
       [ 43.670158, -79.507366],
       [ 43.687972, -79.301815]])

Function to create Foursquare API Request

In [27]:
def getVenuesSearch(query, latitudes, longitudes, radius = 15000, LIMIT = 50):
    
    
    search_results_all = pd.DataFrame(columns=['id','address', 'lat', 'lng'])
    i = 0
    
    for latitude, longitude in zip(latitudes, longitudes):
        print('centroid: ', i, latitude, longitude)
            
        # create the API request URL
        url = 'https://api.foursquare.com/v2/venues/search?&client_id={}&client_secret={}&v={}&ll={},{}&radius={}&limit={}&query={}'.format(
            CLIENT_ID, 
            CLIENT_SECRET, 
            VERSION, 
            latitude, 
            longitude, 
            radius, 
            LIMIT,
            query)
        print(url) # display URL
                  
            
        # make the GET request
        results = requests.get(url).json()
        search_results = results['response']['venues']
        search_results_df = pd.json_normalize(search_results) # flatten JSON

        # filter columns
        filtered_columns = ['id', 'location.address', 'location.lat', 'location.lng']
        nearby_search_venues = search_results_df.loc[:, filtered_columns]

        # clean columns
        nearby_search_venues.columns = [col.split(".")[-1] for col in nearby_search_venues.columns]
        
        search_results_all = pd.concat([search_results_all, nearby_search_venues])
        print('search_results_all size: ', search_results_all.shape)
        i = i +1

    search_results_all = search_results_all.drop_duplicates(subset='id', ignore_index=True)
    print('search_results_all size: ', search_results_all.shape)
          
    return(search_results_all)
In [121]:
# getVenuesSearch function

# Run the above function
# Supply venue name query string - Example 'Starbucks'
# Supply numpy array of latitude & longitude values


query = 'Starbucks'
all_starbucks = getVenuesSearch(query, latitudes=coordinates[:, 0], longitudes=coordinates[:, 1])
centroid:  0 43.777008 -79.224105
https://api.foursquare.com/v2/venues/search?&client_id=1O1KN544PAGAVX5SIEEO4SINNIYJTELH1YMHPSOPLESWN1VF&client_secret=3VB4HIYI1IYA0SYA5L3TLAJM0P4KFGOZ45FRMYMSGXCR4FTV&v=20200514&ll=43.777008,-79.224105&radius=15000&limit=50&query=Starbucks
search_results_all size:  (50, 4)
centroid:  1 43.768633 -79.413312
https://api.foursquare.com/v2/venues/search?&client_id=1O1KN544PAGAVX5SIEEO4SINNIYJTELH1YMHPSOPLESWN1VF&client_secret=3VB4HIYI1IYA0SYA5L3TLAJM0P4KFGOZ45FRMYMSGXCR4FTV&v=20200514&ll=43.768633,-79.413312&radius=15000&limit=50&query=Starbucks
search_results_all size:  (100, 4)
centroid:  2 43.653908 -79.384293
https://api.foursquare.com/v2/venues/search?&client_id=1O1KN544PAGAVX5SIEEO4SINNIYJTELH1YMHPSOPLESWN1VF&client_secret=3VB4HIYI1IYA0SYA5L3TLAJM0P4KFGOZ45FRMYMSGXCR4FTV&v=20200514&ll=43.653908,-79.384293&radius=15000&limit=50&query=Starbucks
search_results_all size:  (150, 4)
centroid:  3 43.670158 -79.507366
https://api.foursquare.com/v2/venues/search?&client_id=1O1KN544PAGAVX5SIEEO4SINNIYJTELH1YMHPSOPLESWN1VF&client_secret=3VB4HIYI1IYA0SYA5L3TLAJM0P4KFGOZ45FRMYMSGXCR4FTV&v=20200514&ll=43.670158,-79.507366&radius=15000&limit=50&query=Starbucks
search_results_all size:  (200, 4)
centroid:  4 43.687972 -79.301815
https://api.foursquare.com/v2/venues/search?&client_id=1O1KN544PAGAVX5SIEEO4SINNIYJTELH1YMHPSOPLESWN1VF&client_secret=3VB4HIYI1IYA0SYA5L3TLAJM0P4KFGOZ45FRMYMSGXCR4FTV&v=20200514&ll=43.687972,-79.301815&radius=15000&limit=50&query=Starbucks
search_results_all size:  (250, 4)
search_results_all size:  (160, 4)
In [122]:
all_starbucks.head(20)
Out[122]:
id address lat lng
0 4cc1d28c06c254815ac18547 300 Borough Dr 43.770037 -79.221156
1 5bd738f4cd441c002c230068 1100 Ellesmere Rd 43.768181 -79.271665
2 4d9b165a7668f04d3869c993 43 Milner Ave, Unit 2 43.783742 -79.253186
3 4b06eb1cf964a52005f322e3 255 Morningside Avenue 43.770281 -79.185016
4 4ca610f0931bb60cdcad8fe2 300 Borough Dr, 207 43.776755 -79.259008
5 4ad8bf7df964a5204c1421e3 2555 Victoria Park Avenue 43.773219 -79.321634
6 4ae0e2a7f964a5205f8321e3 861 York Mills Rd, York Mills & Lesmill 43.754199 -79.351382
7 5b5f7dfe93bd63002c9b8645 300 Borough drive 43.776375 -79.257540
8 5be2a490c876c80039857ee3 1015 Sheppard Ave E, Unit 2 43.769700 -79.372830
9 4af746eef964a520ea0722e3 7080 Warden Ave, Bldg. B, Unit 6 Warden Centre 43.822299 -79.325895
10 4b6c612af964a52029352ce3 20 William Kitchen Rd 43.772163 -79.282827
11 4aff00b7f964a520d13222e3 1900 Eglinton Avenue East 43.728053 -79.294142
12 4be499ba8a8cb713cd4cc4a0 201 Main Street 43.868473 -79.312043
13 4aec6bbaf964a520e6c621e3 2630 Yonge St 43.715590 -79.400450
14 4b043657f964a5202c5222e3 446 Spadina Rd 43.688970 -79.413097
15 5c0beea9dd12f8002cd99cc7 NaN 43.832283 -79.405020
16 4be1a9778ea562b5d81a2170 15 Westney Rd, Pad 24 43.859083 -79.039129
17 4ade4a1af964a520ab7421e3 1020 Kingston Rd 43.680880 -79.284970
18 5bdb0df06336be002c8bffd8 1750 Finch Ave 43.795804 -79.349547
19 4aec6b7df964a520e0c621e3 2451 Yonge St N 43.711039 -79.398924
In [123]:
# Map of All Starbucks locations around Toronto

all_starbucks.shape
Out[123]:
(160, 4)
In [124]:
#1 Blue Jays Way
# Toronto, ON M5V 1J3, Canada
# 43.641853, -79.389716

latitude = 43.641853
longitude = -79.389716

In [125]:
#  parse_html=True

All_Starbucks_Map = folium.Map(location=[latitude, longitude], zoom_start=12, control_scale=True)


# I can add markers on the map with popup for Postal Code but borough name will not work reliably with variety of 
# characters, such as / . ''. Use folium.Popup with parse_html


        
# Starbucks Green
for i in range(0,len(all_starbucks)):
    folium.Marker([all_starbucks.iloc[i]['lat'], all_starbucks.iloc[i]['lng']],
                  popup=folium.Popup(all_starbucks.iloc[i]['address'], parse_html=True),
                  icon=folium.Icon(color='green', icon='info-sign')
                  ).add_to(All_Starbucks_Map)   
    
    
    
#map.save('map_All_Starbucks_15km_20200629.html') 

All_Starbucks_Map.save('map_All_Starbucks_15km_20200629.html')
All_Starbucks_Map 
Out[125]:

Map of All 160 Starbucks locations around Toronto

In [ ]:
 
In [ ]:
# k-means_clustering

K-Means Clustering

In [126]:
all_starbucks.columns
Out[126]:
Index(['id', 'address', 'lat', 'lng'], dtype='object')
In [127]:
filtered_columns = ['lat', 'lng']
AllStar_lat_lng_df = all_starbucks.loc[:, filtered_columns]

AllStar_lat_lng_df.head(10)
Out[127]:
lat lng
0 43.770037 -79.221156
1 43.768181 -79.271665
2 43.783742 -79.253186
3 43.770281 -79.185016
4 43.776755 -79.259008
5 43.773219 -79.321634
6 43.754199 -79.351382
7 43.776375 -79.257540
8 43.769700 -79.372830
9 43.822299 -79.325895
In [128]:
# Convert All_Toronto_Starbucks_df to numpy array

AllStar = AllStar_lat_lng_df.to_numpy(copy=True)
AllStar
Out[128]:
array([[ 43.7700372 , -79.22115587],
       [ 43.768181  , -79.271665  ],
       [ 43.783742  , -79.253186  ],
       [ 43.770281  , -79.185016  ],
       [ 43.776755  , -79.259008  ],
       [ 43.773219  , -79.321634  ],
       [ 43.754199  , -79.351382  ],
       [ 43.776375  , -79.25754   ],
       [ 43.7697    , -79.37283   ],
       [ 43.822299  , -79.325895  ],
       [ 43.772163  , -79.282827  ],
       [ 43.728053  , -79.294142  ],
       [ 43.868473  , -79.312043  ],
       [ 43.71559   , -79.40045   ],
       [ 43.68897   , -79.413097  ],
       [ 43.832283  , -79.40502   ],
       [ 43.859083  , -79.039129  ],
       [ 43.68088   , -79.28497   ],
       [ 43.795804  , -79.349547  ],
       [ 43.711039  , -79.398924  ],
       [ 43.77799   , -79.344091  ],
       [ 43.758015  , -79.409613  ],
       [ 43.783821  , -79.18749   ],
       [ 43.75419547, -79.35764132],
       [ 43.768353  , -79.413046  ],
       [ 43.68397177, -79.41891458],
       [ 43.868474  , -79.229871  ],
       [ 43.836186  , -79.086041  ],
       [ 43.71062469, -79.36119843],
       [ 43.769468  , -79.362582  ],
       [ 43.885178  , -79.372822  ],
       [ 43.706564  , -79.359591  ],
       [ 43.660047  , -79.378802  ],
       [ 43.713756  , -79.365021  ],
       [ 43.89589371, -79.28511936],
       [ 43.795804  , -79.349547  ],
       [ 43.82261   , -79.35059   ],
       [ 43.769148  , -79.386238  ],
       [ 43.874106  , -79.260842  ],
       [ 43.836606  , -79.090204  ],
       [ 43.707485  , -79.381479  ],
       [ 43.845919  , -79.381723  ],
       [ 43.76699379, -79.41078775],
       [ 43.869088  , -79.287153  ],
       [ 43.706859  , -79.39695   ],
       [ 43.849284  , -79.354996  ],
       [ 43.72982   , -79.40365   ],
       [ 43.710741  , -79.36068   ],
       [ 43.707269  , -79.3996    ],
       [ 43.79571589, -79.42244003],
       [ 43.768464  , -79.414017  ],
       [ 43.768353  , -79.413046  ],
       [ 43.755797  , -79.440471  ],
       [ 43.780178  , -79.416408  ],
       [ 43.761077  , -79.411483  ],
       [ 43.762508  , -79.410531  ],
       [ 43.79656145, -79.42228615],
       [ 43.66498   , -79.38051   ],
       [ 43.73758068, -79.43424457],
       [ 43.793986  , -79.445416  ],
       [ 43.728673  , -79.418513  ],
       [ 43.766762  , -79.475957  ],
       [ 43.657656  , -79.385007  ],
       [ 43.816777  , -79.452391  ],
       [ 43.724878  , -79.40249   ],
       [ 43.671082  , -79.380756  ],
       [ 43.896239  , -79.403953  ],
       [ 43.661527  , -79.383411  ],
       [ 43.668615  , -79.397387  ],
       [ 43.704171  , -79.411887  ],
       [ 43.662407  , -79.385943  ],
       [ 43.72567   , -79.452168  ],
       [ 43.700598  , -79.427433  ],
       [ 43.669963  , -79.386158  ],
       [ 43.817307  , -79.424102  ],
       [ 43.67802   , -79.39024   ],
       [ 43.655588  , -79.385491  ],
       [ 43.650751  , -79.388047  ],
       [ 43.647261  , -79.378599  ],
       [ 43.65596912, -79.38268427],
       [ 43.65561447, -79.38059969],
       [ 43.652221  , -79.378704  ],
       [ 43.65194552, -79.38187827],
       [ 43.654438  , -79.380699  ],
       [ 43.655536  , -79.380933  ],
       [ 43.649611  , -79.385305  ],
       [ 43.653716  , -79.380338  ],
       [ 43.656747  , -79.380485  ],
       [ 43.65446529, -79.37891894],
       [ 43.65772451, -79.38051793],
       [ 43.648876  , -79.38554   ],
       [ 43.64645   , -79.391759  ],
       [ 43.6538975 , -79.38424034],
       [ 43.650043  , -79.391011  ],
       [ 43.648528  , -79.383335  ],
       [ 43.650159  , -79.377793  ],
       [ 43.65907956, -79.38056192],
       [ 43.65945605, -79.39041123],
       [ 43.648643  , -79.379025  ],
       [ 43.64974325, -79.37943079],
       [ 43.658204  , -79.388998  ],
       [ 43.649928  , -79.383247  ],
       [ 43.645365  , -79.389635  ],
       [ 43.64666   , -79.382071  ],
       [ 43.649328  , -79.378339  ],
       [ 43.665207  , -79.384372  ],
       [ 43.646799  , -79.38069   ],
       [ 43.64902832, -79.3815929 ],
       [ 43.648826  , -79.379207  ],
       [ 43.65484102, -79.37581464],
       [ 43.6457132 , -79.381193  ],
       [ 43.65398904, -79.38405508],
       [ 43.64894287, -79.3907915 ],
       [ 43.64493   , -79.38338   ],
       [ 43.64873831, -79.37251925],
       [ 43.64797   , -79.39669   ],
       [ 43.64694617, -79.38249485],
       [ 43.660887  , -79.39372   ],
       [ 43.6471926 , -79.38095686],
       [ 43.64621307, -79.37908847],
       [ 43.646597  , -79.513914  ],
       [ 43.64835   , -79.50768   ],
       [ 43.640187  , -79.538053  ],
       [ 43.660005  , -79.513883  ],
       [ 43.665585  , -79.49533   ],
       [ 43.651395  , -79.47599   ],
       [ 43.667391  , -79.488911  ],
       [ 43.665346  , -79.471883  ],
       [ 43.634136  , -79.41185   ],
       [ 43.623158  , -79.482642  ],
       [ 43.653908  , -79.600009  ],
       [ 43.67196956, -79.59174214],
       [ 43.65346   , -79.42644   ],
       [ 43.570161  , -79.602699  ],
       [ 43.641362  , -79.414053  ],
       [ 43.65347   , -79.45159   ],
       [ 43.70067467, -79.4336732 ],
       [ 43.67390979, -79.43041984],
       [ 43.67153   , -79.4214    ],
       [ 43.64429   , -79.41906   ],
       [ 43.665638  , -79.409922  ],
       [ 43.71506956, -79.58187258],
       [ 43.72867   , -79.608169  ],
       [ 43.689422  , -79.578078  ],
       [ 43.612038  , -79.578298  ],
       [ 43.59442736, -79.53338424],
       [ 43.664499  , -79.399515  ],
       [ 43.64006   , -79.419812  ],
       [ 43.636679  , -79.400321  ],
       [ 43.688502  , -79.288239  ],
       [ 43.678879  , -79.346357  ],
       [ 43.65972   , -79.328256  ],
       [ 43.660742  , -79.342362  ],
       [ 43.680167  , -79.339669  ],
       [ 43.70519   , -79.37476   ],
       [ 43.642743  , -79.374104  ],
       [ 43.670627  , -79.390337  ],
       [ 43.686152  , -79.392874  ],
       [ 43.670678  , -79.382457  ],
       [ 43.67062   , -79.386304  ]])
In [129]:
AllStar.shape
Out[129]:
(160, 2)
In [130]:
kmeans = KMeans(n_clusters=4)
kmeans.fit(AllStar)
y_kmeans = kmeans.predict(AllStar)
In [136]:
# Latitude - equator=0 to poles=90 (Array 0, first value in coordinate) is represented on graph as Y axis
# Longitude - (Array 1 values, 2nd in pair ) East West, Greenwich, England, is defined as 0° longitude with west longitudes being negative. X axis on graph plot.


plt.figure(figsize=(16,9))
plt.scatter(AllStar[:, 1], AllStar[:, 0], c=y_kmeans, s=50, cmap='viridis')

centers = kmeans.cluster_centers_
plt.scatter(centers[:, 1], centers[:, 0], c='black', s=200, alpha=0.5);

plt.colorbar();  # show color scale
#plt.gca().invert_yaxis()
In [132]:
print(centers)
[[ 43.66523922 -79.38441197]
 [ 43.81932906 -79.21926814]
 [ 43.78435581 -79.39676533]
 [ 43.65315762 -79.53897282]]
In [133]:
y_kmeans
Out[133]:
array([1, 1, 1, 1, 1, 2, 2, 1, 2, 2, 1, 0, 1, 0, 0, 2, 1, 0, 2, 0, 2, 2,
       1, 2, 2, 0, 1, 1, 0, 2, 2, 0, 0, 0, 1, 2, 2, 2, 1, 1, 0, 2, 2, 1,
       0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 2, 2, 0,
       2, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 3, 3,
       0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0], dtype=int32)
In [ ]:
 

Predict cluster label for a new coordinate

In [134]:
# Where I want to go:
# Starbucks Reserve Bar
# 1090 Don Mills Rd (at Lawrence Ave)
# Toronto ON M3C 0H1
# Canada
third_test = np.array([43.735977, -79.344265])
third_test = third_test.reshape(1, -1)
third_test_label = kmeans.predict(third_test)
third_test_label
Out[134]:
array([2], dtype=int32)
In [ ]:
# Starbucks Reserve Bar is in Cluster 2
In [137]:
plt.figure(figsize=(16,9))
plt.scatter(AllStar[:, 1], AllStar[:, 0], c=y_kmeans, s=50, cmap='viridis')

centers = kmeans.cluster_centers_
plt.scatter(centers[:, 1], centers[:, 0], c='black', s=200, alpha=0.5)
plt.scatter(third_test[:, 1], third_test[:, 0], c=third_test_label, s=750, cmap='viridis', alpha=0.25)

plt.colorbar();  # show color scale
In [ ]:
# Starbucks Reserve Bar highlighted with larger diameter, .25 alpha circle marker.

Now Display All Starbucks locations color coded by K-Means Cluster

In [138]:
kcolors = ['green', 'orange', 'purple', 'darkblue']
In [139]:
kcolors
Out[139]:
['green', 'orange', 'purple', 'darkblue']
In [ ]:
 
In [145]:
#  parse_html=True

latitude = 43.735977
longitude = -79.344265

All_Starbucks_Map = folium.Map(location=[latitude, longitude], zoom_start=11, control_scale=True)


# I can add markers on the map with popup for Postal Code but borough name will not work reliably with variety of 
# characters, such as / . ''. Use folium.Popup with parse_html


        
# Starbucks Green
for i in range(0,len(all_starbucks)):
    folium.Marker([all_starbucks.iloc[i]['lat'], all_starbucks.iloc[i]['lng']],
                  popup=folium.Popup(all_starbucks.iloc[i]['address'], parse_html=True),
                  icon=folium.Icon(color=kcolors[y_kmeans[i]], icon='info-sign')
                  ).add_to(All_Starbucks_Map)   
   
# y_kmeans markers for cluster centers

for i in range(0, len(centers)):
    folium.Marker([centers[i, 0], centers[i, 1]],
             icon=folium.Icon(color='black', icon='info-sign') 
             ).add_to(All_Starbucks_Map)     
    
folium.Marker([third_test[:, 0], third_test[:, 1]],
              popup=folium.Popup('Starbucks Reserve Bar'),
             icon=folium.Icon(color='darkpurple', icon='info-sign') 
             ).add_to(All_Starbucks_Map)     
     
All_Starbucks_Map.save('map_All_Starbucks_Clusters_20200629.html')    
All_Starbucks_Map
Out[145]:

Starbucks locations color coded by K-means cluster it belongs to. Starbucks Reserve Bar icon in dark purple.

In [ ]:
 
In [ ]:
 

In [148]:
import json

# load geo_json
# shapefiles can be converted to geojson with QGIS
with open('Neighbourhoods.geojson') as f:
    boundaries_geo = json.load(f)
    
# add feature 'id' county name to geojson
# access features
for i in boundaries_geo['features']:
    i['id'] = i['properties']['AREA_SHORT_CODE']
    
new_boundaries_geo = boundaries_geo    
    
# load data associated with geo_json

    
# map 43.735977, -79.344265   
map_AllStar_choropleth = folium.Map(location=[43.735977, -79.344265], zoom_start=12)

# choropleth
folium.Choropleth(
    geo_data=new_boundaries_geo,
    name='choropleth',
    data=df,
    columns=['NeighbourhoodNumber','PopulationDensity_per_square_km'],
    # see folium.Choropleth? for details on key_on
    # key_on='feature.id',
    key_on='feature.properties.AREA_SHORT_CODE',
    fill_color='BuPu',
    #threshold_scale=[0, 2, 4, 8, 16, 32],
    #bins=9,
    #bins=[1000, 2000, 3000, 4000, 5000, 7500, 10000, 15000, 45000],
    bins=[1040, 2853, 3882, 4717, 6300, 7600, 45000],
    fill_opacity=0.5,
    line_opacity=0.5,
    legend_name='Population',
    highlight=True
).add_to(map_AllStar_choropleth)

# layer control to turn choropleth on or off
folium.LayerControl().add_to(map_AllStar_choropleth)




# Starbucks Green
for i in range(0,len(all_starbucks)):
    folium.Marker([all_starbucks.iloc[i]['lat'], all_starbucks.iloc[i]['lng']],
                  popup=folium.Popup(all_starbucks.iloc[i]['address'], parse_html=True),
                  icon=folium.Icon(color=kcolors[y_kmeans[i]], icon='info-sign')
                  ).add_to(map_AllStar_choropleth)   
   
# y_kmeans

for i in range(0, len(centers)):
    folium.Marker([centers[i, 0], centers[i, 1]],
             icon=folium.Icon(color='black', icon='info-sign') 
             ).add_to(map_AllStar_choropleth)     
    
    

# display map
map_AllStar_choropleth.save('map_All_Starbucks_Clusters_PopDensity_20200629.html')
map_AllStar_choropleth
Out[148]:

Starbucks K-Means Clusters marked on Population Density Map for Toronto's Neighbourhoods

In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]: